package cn.ycc1.baseset.classes.lambdas;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

/**
 * Lambda Expression
 * @author ycc
 * @date 2025/3/6
 * In 2014, Java SE 8 saw the introduction of the concept of lambda expressions. If you remember the days before Java SE 8 was
 * released, then you probably remember the anonymous classes concept. And maybe you have heard that lambda expressions are
 * another, simpler way of writing instances of anonymous classes, in some precise cases.
 *
 * If you do not remember those days, then you may have heard or read about anonymous classes, and are probably afraid of this
 * obscure syntax.
 *
 * Well, the good news is: you do not need to go through anonymous classes to understand how to write a lambda expression.
 * Moreover, in many cases, thanks to the addition of lambdas to the Java language, you do not need anonymous classes anymore.
 *
 * Writing a lambda expression breaks down to understanding three steps:
 *
 * identifying the type of the lambda expression you want to write
 * finding the right method to implement
 * implementing this method.
 * This is really all there is to it. Let us see these three steps in detail.
 */
public class LambdaExpression {
    /**
     * Identifying the Type of a Lambda Expression
     * Everything has a type in the Java language, and this type is known at compile time. So it is always possible to find the
     * type of a lambda expression. It may be the type of a variable, of a field, of a method parameter, or the returned type
     * of a method.
     *
     * There is a restriction on the type of a lambda expression: it has to be a functional interface. So an anonymous class that
     * does not implement a functional interface cannot be written as a lambda expression.
     *
     * The complete definition of what functional interfaces are is a little complex. All you need to know at this point is
     * that a functional interface is an interface that has only one abstract method.
     *
     * You should be aware that, starting with Java SE 8, concrete methods are allowed in interfaces. They can be instance
     * methods, in that case, they are called default methods, and they can be static methods. These methods do not count,
     * since they are not abstract methods.
     *
     * Do I need to add the annotation @FunctionalInterface on an interface to make it functional?
     *
     * No you don't. This annotation is here to help you to make sure that your interface is indeed functional. If you put
     * this annotation on a type that is not a functional interface, then the compiler will raise an error.
     *
     * Examples of Functional Interfaces
     * Let us see some examples taken from the JDK API. We have just removed the comments from the source code.
     *
     * @FunctionalInterface
     * public interface Runnable {
     *     public abstract void run();
     * }
     * The Runnable interface is indeed functional, because it has only one abstract method. The @FunctionalInterface
     * annotation has been added as a helper, but it is not needed.
     *
     * @FunctionalInterface
     * public interface Consumer<T> {
     *
     *     void accept(T t);
     *
     *     default Consumer<T> andThen(Consumer<? super T> after) {
     *         // the body of this method has been removed
     *     }
     * }
     * The Consumer<T> interface is also functional: it has one abstract method and one default, concrete method that does
     * not count. Once again, the @FunctionalInterface annotation is not needed.
     *
     * @FunctionalInterface
     * public interface Predicate<T> {
     *
     *     boolean test(T t);
     *
     *     default Predicate<T> and(Predicate<? super T> other) {
     *         // the body of this method has been removed
     *     }
     *
     *     default Predicate<T> negate() {
     *         // the body of this method has been removed
     *     }
     *
     *     default Predicate<T> or(Predicate<? super T> other) {
     *         // the body of this method has been removed
     *     }
     *
     *     static <T> Predicate<T> isEqual(Object targetRef) {
     *         // the body of this method has been removed
     *     }
     *
     *     static <T> Predicate<T> not(Predicate<? super T> target) {
     *         // the body of this method has been removed
     *     }
     * }
     * The Predicate<T> interface is a little more complex, but it is still a functional interface:
     *
     * it has one abstract method
     * it has three default methods that do not count
     * and it has two static methods that do not count neither.
     */

    /**
     * Finding the Right Method to Implement
     * At this point you have identified the type of the lambda expression you need to write, and the good news is: you have
     * done the hardest part: the rest is very mechanical and easier to do.
     *
     * A lambda expression is an implementation of the only abstract method in this functional interface. So finding the
     * right method to implement is just a matter of finding this method.
     *
     * You can take a minute to look for it in the three examples of the previous paragraph.
     *
     * For the Runnable interface it is:
     *
     * public abstract void run();
     * For the Predicate interface it is:
     *
     * boolean test(T t);
     * And for the Consumer<T> interface it is:
     *
     * void accept(T t);
     */

    /**
     * Implementing the Right Method with a Lambda Expression
     * Writing a First Lambda Expression that implements Predicate<String>
     * Now for the last part: writing the lambda itself. What you need to understand is that the lambda expression you are
     * writing is an implementation of the abstract method you found. Using the lambda expression syntax, you can nicely
     * inline this implementation in your code.
     *
     * This syntax is made of three elements:
     *
     * a block of parameters;
     * a little piece of ASCII art depicting an arrow: ->. Note that Java uses meager arrows (->) and not fat arrows (=>);
     * a block of code which is the body of the method.
     * Let us see examples of this. Suppose you need an instance of a Predicate that returns true for strings of characters
     * that have exactly 3 characters.
     *
     * The type of your lambda expression is Predicate
     * The method you need to implement is boolean test(String s)
     * Then you write the block of parameters, which is simple copy / paste of the signature of the method: (String s).
     *
     * You then add a meager arrow: ->.
     *
     * And the body of the method. Your result should look like this:
     *
     * Predicate<String> predicate =
     *     (String s) -> {
     *         return s.length() == 3;
     *     };
     * Simplifying the Syntax
     * This syntax can then be simplified, thanks to the compiler that can guess many things so that you do not need to write
     * them.
     *
     * First, the compiler knows that you are implementing the abstract method of the Predicate interface, and it knows that
     * this method takes a String as a argument. So (String s) can be simplified to (s). In that case, where there is only one
     * argument, you can even go one step further by removing the parentheses. The block of arguments then becomes s. You
     * should keep the parentheses if you have more than one argument, or no argument.
     *
     * Second, there is just one line of code in the body of the method. In that case you do not need the curly braces nor
     * the return keyword.
     *
     * So the final syntax is in fact the following:
     *
     * Predicate<String> predicate = s -> s.length() == 3;
     * And this leads us to the first good practice: keep your lambdas short, so that they are just one line of simple,
     * readable code.
     *
     * Implementing a Consumer<String>
     * At some point, people may be tempted to take shortcuts. You will hear developers saying "a consumer takes an object
     * and returns nothing". Or "a predicate is true when the string has exactly three characters". Most of the time,
     * there is a confusion between the lambda expression, the abstract method it implements, and the functional interface
     * that holds this method.
     *
     * But since a functional interface, its abstract method, and a lambda expression that implements it are so closely
     * linked together, this way of speaking actually makes total sense. So that's OK, as long as it does not lead to any
     * ambiguity.
     *
     * Let us write a lambda that consumes a String and prints on System.out. The syntax can be this one:
     *
     * Consumer<String> print = s -> System.out.println(s);
     * Here we directly wrote the simplified version of the lambda expression.
     *
     * Implementing a Runnable
     * Implementing a Runnable turns out to write an implementation of void run(). This block of arguments is empty,
     * so it should be written with parentheses. Remember: you can omit the parentheses only if you have one argument,
     * here we have zero.
     *
     * So let us write a runnable that tells us that it is running:
     *
     * Runnable runnable = () -> System.out.println("I am running");
     */

    /**
     * Calling a Lambda Expression
     * Let us go back to our previous Predicate example, and suppose that this predicate has been defined in a method. How can you use it to test if a given string of characters is indeed of length 3?
     *
     * Well, despite the syntax you used to write a lambda, you need to keep in mind that this lambda is an instance of the interface Predicate. This interface defines a method called test() that takes a String and returns a boolean.
     *
     * Let us write that in a method:
     */
    List<String> retainStringsOfLength3(List<String> strings) {

        Predicate<String> predicate = s -> s.length() == 3;
        List<String> stringsOfLength3 = new ArrayList<>();
        for (String s: strings) {
            if (predicate.test(s)) {
                stringsOfLength3.add(s);
            }
        }
        return stringsOfLength3;
    }
    /**
     * Note how you defined the predicate, just as you did in the previous example. Since the Predicate interface defines
     * this method boolean test(String), it is perfectly legal to call the methods defined in Predicate through a variable
     * of type Predicate. This may look confusing at first, since this predicate variable does not look like it defines methods.
     *
     * Bear with us, there are much better way to write this code, that you will see later in this tutorial.
     *
     * So everytime you write a lambda, you can call any method defined on the interface this lamdba is implementing.
     * Calling the abstract method will call the code of your lambda itself, since this lambda is an implementation of
     * that method. Calling a default method will call the code written in the interface. There is no way a lambda can
     * override a default method.
     */

    /**
     * Capturing Local Values
     * Once you get used to them, writing lambdas becomes very natural. They are very well integrated in the Collections
     * Framework, in the Stream API and in many other places in the JDK. Starting with Java SE 8, lambdas are everywhere,
     * for the best.
     *
     * There are constraints in using lambdas and you may come across compile-time errors that you need to understand.
     *
     * Let us consider the following code:
     *
     * int calculateTotalPrice(List<Product> products) {
     *
     *     int totalPrice = 0;
     *     Consumer<Product> consumer =
     *         product -> totalPrice += product.getPrice();
     *     for (Product product: products) {
     *         consumer.accept(product);
     *     }
     * }
     * Even if this code may look nice, trying to compile it will give you the following error on the use of totalPrice in
     * this Consumer implementation:
     *
     * Variable used in lambda expression should be final or effectively final
     *
     * The reason is the following: lambdas cannot modify variables defined outside their body. They can read them,
     * as long as they are final, that is, immutable. This process of accessing variable is called capturing: lambdas
     * cannot capture variables, they can only capture values. A final variable is in fact a value.
     *
     * You have noted that the error message tells us that the variable can be final, which is a classical concept in the
     * Java language. It also tells us that the variable can be effectively final. This notion was introduced in
     * Java SE 8: even if you do not explicitly declare a variable final, the compiler may do it for you.
     * If it sees that this variable is read from a lambda, and that you do not modify it, then it will nicely add the
     * final declaration for you. Of course this is done in the compiled code, the compiler does not modify your source code.
     * Such variables are not called final; they are called effectively final variables. This is a very useful feature.
     */

    /**
     * Serializing Lambdas
     * Lambda expressions have been built so that they can be serialized.
     *
     * Why would you serialize lambda expressions? Well, a lambda expression may be stored in a field, and this field
     * may be accessed through a constructor or a setter method. Then you may have a lambda expression in the state of
     * your object at runtime, without being aware of it.
     *
     * So to preserve backward compatibility with existing serializable classes, serializing a lambda expression is possible.
     *
     * 序列化Lambdas
     * Lambda表达式已经构建，因此可以序列化。
     * 为什么要序列化lambda表达式？一个lambda表达式可以存储在一个字段中，这个字段可以通过构造函数或setter方法访问。然后，
     * 您可能在运行时对象的状态中有一个lambda表达式，而不知道它。
     * 因此，为了保持与现有可序列化类的向后兼容性，可以序列化lambda表达式。
     */

}
